using UnityEngine;
using System.Collections.Generic;

public class GameBoard : MonoBehaviour
{
    [SerializeField] Transform ground = default;

    [SerializeField] GameTile tilePrefab = default;
    
    [SerializeField]
    Texture2D gridTexture = default;

    Vector2Int size;

    GameTile[] tiles;
    
    bool showGrid, showPaths;

    Queue<GameTile> searchFrontier = new Queue<GameTile>();
    
    GameTileContentFactory contentFactory;
    
    List<GameTile> spawnPoints = new List<GameTile>();
    
    List<GameTileContent> updatingContent = new List<GameTileContent>();
    
    public void GameUpdate () {
        for (int i = 0; i < updatingContent.Count; i++) {
            updatingContent[i].GameUpdate();
        }
    }
    
    public bool ShowGrid {
        get => showGrid;
        set {
            showGrid = value;
            Material m = ground.GetComponent<MeshRenderer>().material;
            if (showGrid) {
                m.mainTexture = gridTexture;
                m.SetTextureScale("_MainTex", size);
            }
            else {
                m.mainTexture = null;
            }
        }
    }
    
    public bool ShowPaths {
        get => showPaths;
        set {
            showPaths = value;
            if (showPaths) {
                foreach (GameTile tile in tiles) {
                    tile.ShowPath();
                }
            }
            else {
                foreach (GameTile tile in tiles) {
                    tile.HidePath();
                }
            }
        }
    }

    /// 初始化网格大小和瓦片布局
    /// <param name="size">网格的大小，由x和y轴的格子数定义</param>
    public void Initialize(Vector2Int size, GameTileContentFactory contentFactory)
    {
        // 设置网格的大小
        this.size = size;
        this.contentFactory = contentFactory;
        // 设置地面对象的缩放，以匹配网格大小
        ground.localScale = new Vector3(size.x, size.y, 1f);

        // 计算网格的偏移量，用于将网格中心对齐到坐标原点
        Vector2 offset = new Vector2((size.x - 1) * 0.5f, (size.y - 1) * 0.5f);

        // 初始化瓦片数组，大小为网格中所有格子的数量
        tiles = new GameTile[size.x * size.y];

        // 遍历网格中的每个位置，实例化游戏瓦片并进行布局
        for (int i = 0, y = 0; y < size.y; y++)
        {
            for (int x = 0; x < size.x; x++, i++)
            {
                // 实例化瓦片对象
                GameTile tile = tiles[i] = Instantiate(tilePrefab);
                // 将实例化的瓦片对象的父对象设置为当前对象，不保持本地变换
                tile.transform.SetParent(transform, false);
                // 计算并设置瓦片对象的本地位置，使其以网格的形式布局
                tile.transform.localPosition = new Vector3(x - offset.x, 0f, y - offset.y);
                // 检查当前位置的东邻接方块
                if (x > 0)
                {
                    // 如果存在东侧方块，则建立东侧和西侧方块的邻接关系
                    GameTile.MakeEastWestNeighbors(tile, tiles[i - 1]);
                }

                // 检查当前位置的南邻接方块
                if (y > 0)
                {
                    // 如果存在南侧方块，则建立北侧和南侧方块的邻接关系
                    GameTile.MakeNorthSouthNeighbors(tile, tiles[i - size.x]);
                }
                
                tile.IsAlternative = (x & 1) == 0;
                if ((y & 1) == 0) {
                    tile.IsAlternative = !tile.IsAlternative;
                }
            }
        }

        Clear();
    }
    
    public void Clear () {
        foreach (GameTile tile in tiles) {
            tile.Content = contentFactory.Get(GameTileContentType.Empty);
        }
        spawnPoints.Clear();
        updatingContent.Clear();
        ToggleDestination(tiles[tiles.Length / 2]);
        ToggleSpawnPoint(tiles[0]);
    }

    /// <summary>
    /// 初始化搜索路径。
    /// </summary>
    /// <remarks>
    /// 此方法首先清除所有游戏瓦片的路径标记，然后将起始瓦片标记为目标，
    /// 并将其加入到搜索前沿队列中，为接下来的路径搜索做准备。
    /// </remarks>
    bool FindPaths()
    {
        // 遍历所有游戏瓦片，清除每块瓦片的路径信息
        foreach (GameTile tile in tiles)
        {
            if (tile.Content.Type == GameTileContentType.Destination) {
                tile.BecomeDestination();
                searchFrontier.Enqueue(tile);
            }
            else {
                tile.ClearPath();
            }
        }
        if (searchFrontier.Count == 0) {
            return false;
        }

        while (searchFrontier.Count > 0)
        {
            GameTile tile = searchFrontier.Dequeue();
            if (tile != null) {
                if (tile.IsAlternative) {
                    searchFrontier.Enqueue(tile.GrowPathNorth());
                    searchFrontier.Enqueue(tile.GrowPathSouth());
                    searchFrontier.Enqueue(tile.GrowPathEast());
                    searchFrontier.Enqueue(tile.GrowPathWest());
                }
                else {
                    searchFrontier.Enqueue(tile.GrowPathWest());
                    searchFrontier.Enqueue(tile.GrowPathEast());
                    searchFrontier.Enqueue(tile.GrowPathSouth());
                    searchFrontier.Enqueue(tile.GrowPathNorth());
                }
            }
        }
        
        foreach (GameTile tile in tiles) {
            if (!tile.HasPath) {
                return false;
            }
        }
        
        if (showPaths) {
            foreach (GameTile tile in tiles) {
                tile.ShowPath();
            }
        }
        return true;
    }
    
    public GameTile GetTile (Ray ray) {
        if (Physics.Raycast(ray, out RaycastHit hit, float.MaxValue, 1)) {
            int x = (int)(hit.point.x + size.x * 0.5f);
            int y = (int)(hit.point.z + size.y * 0.5f);
            if (x >= 0 && x < size.x && y >= 0 && y < size.y)
            {
                return tiles[x + y * size.x];
            }
        }
        
        return null;
    }
    
    public void ToggleDestination (GameTile tile) {
        if (tile.Content.Type == GameTileContentType.Destination) {
            tile.Content = contentFactory.Get(GameTileContentType.Empty);
            if (!FindPaths()) {
                tile.Content =
                    contentFactory.Get(GameTileContentType.Destination);
                FindPaths();
            }
        }
        else if (tile.Content.Type == GameTileContentType.Empty){
            tile.Content = contentFactory.Get(GameTileContentType.Destination);
            FindPaths();
        }
    }
    
    public void ToggleWall (GameTile tile) {
        if (tile.Content.Type == GameTileContentType.Wall) {
            tile.Content = contentFactory.Get(GameTileContentType.Empty);
            FindPaths();
        }
        else if (tile.Content.Type == GameTileContentType.Empty) {
            tile.Content = contentFactory.Get(GameTileContentType.Wall);
            if (!FindPaths()) {
                tile.Content = contentFactory.Get(GameTileContentType.Empty);
                FindPaths();
            }
        }
    }
    
    public void ToggleSpawnPoint (GameTile tile) {
        if (tile.Content.Type == GameTileContentType.SpawnPoint) {
            if (spawnPoints.Count > 1) {
                spawnPoints.Remove(tile);
                tile.Content = contentFactory.Get(GameTileContentType.Empty);
            }
        }
        else if (tile.Content.Type == GameTileContentType.Empty) {
            tile.Content = contentFactory.Get(GameTileContentType.SpawnPoint);
            spawnPoints.Add(tile);
        }
    }
    
    public void ToggleTower (GameTile tile, TowerType towerType) {
        if (tile.Content.Type == GameTileContentType.Tower) {
            updatingContent.Remove(tile.Content);
            if (((Tower)tile.Content).TowerType == towerType) {
                tile.Content = contentFactory.Get(GameTileContentType.Empty);
                FindPaths();
            }
            else {
                tile.Content = contentFactory.Get(towerType);
                updatingContent.Add(tile.Content);
            }
        }
        else if (tile.Content.Type == GameTileContentType.Empty) {
            tile.Content = contentFactory.Get(towerType);
            if (FindPaths()) {
                updatingContent.Add(tile.Content);
            }
            else {
                tile.Content = contentFactory.Get(GameTileContentType.Empty);
                FindPaths();
            }
        }
        else if (tile.Content.Type == GameTileContentType.Wall) {
            tile.Content = contentFactory.Get(towerType);
            updatingContent.Add(tile.Content);
        }
    }
    
    public GameTile GetSpawnPoint (int index) {
        return spawnPoints[index];
    }
    
    public int SpawnPointCount => spawnPoints.Count;
}